/*
 * @Description: 
 * @Autor: ilmari
 * @Date: 2021-05-18 09:32:18
 * @LastEditors: ilmari
 * @LastEditTime: 2021-05-18 10:17:04
 */
import 'dart:convert';
import 'dart:io';
import 'dart:math';

import 'package:rcmd/app/modules/rcmd/models/rlog.dart';
import 'package:get/get.dart';

import '../ssh_service.dart';

const int _PLUS = 43; // +
const int _SUB = 45; // -

class ConsoleController extends GetxController {
  final List<RlogSection> _lines = [];
  final List<FilterRuler> _dispFilters = [];
  final allLinesCount = 0.obs;
  final dispLinesCount = 0.obs;
  final dispFilterCount = 0.obs;
  int beforeCnt = 0;
  int afterCnt = 0;
  String _hilight = "";

  void copyDisplay(ConsoleController controller) {
    _lines.clear();
    RlogSection sec = RlogSection("");
    controller.dispLines.forEach((line) {
      if (line.runtimeType == RlogSection) {
        sec.finish = true;
        sec = addSection(line.rawText);
      } else {
        sec.addLine(RlogText(line.rawText, sec));
      }
    });
    sec.finish = true;
  }

  void loadLines(String path) {
    removeAllLogLines();
    final f = File(path);
    RlogSection sec = RlogSection("");
    f
        .openRead()
        .transform(utf8.decoder)
        .transform(LineSplitter())
        .listen((line) {
      final t = line.isEmpty ? 0 : line.codeUnitAt(0);
      if (t == _PLUS) {
        sec.finish = true;
        sec = addSection(line.substring(1));
      } else if (t == _SUB) {
        sec.addLine(RlogText(line.substring(1), sec));
      } else if (sec.childrenCount == 0) {
        sec.rawText += "\n$line";
      }
    }).onDone(() {
      sec.finish = true;
      refresh();
    });
  }

  void saveLines(String path) {
    final f = File(path).openWrite();
    final lines = () sync* {
      for (int i = 0; i < _lines.length; i++) {
        final sec = _lines[i];
        yield "+${sec.rawText}";
        for (int j = 0; j < sec.childrenCount; j++) {
          yield "-${sec.childAt(j).rawText}";
        }
      }
    };
    try {
      f.writeAll(lines(), "\n");
    } catch (e) {
      print(e.toString());
    }
    f.close();
  }

  Iterable<Rlogline> get dispLines => _lines.fold(
      <Rlogline>[],
      (previousValue, element) => previousValue
          .followedBy([element]).followedBy(element.displayChilren));

  int get caculateDispLinesCount {
    return _lines.fold(
        0,
        (previousValue, element) =>
            previousValue + 1 + element.dispChildrenCount);
  }

  int get caculateAllLinesCount {
    return _lines.fold(0,
        (previousValue, element) => previousValue + 1 + element.childrenCount);
  }

  RlogSection addSection(String text) {
    final line = RlogSection(text);
    _lines.add(line);
    dispLinesCount.value = caculateDispLinesCount;
    allLinesCount.value = caculateAllLinesCount;
    return line;
  }

  RlogSection _makesureSection() {
    if (_lines.isEmpty || !_lines.last.pending()) {
      return addSection("");
    }
    return _lines.last.section;
  }

  void addLine(String text, {RlogSection? section}) {
    final sec = section ?? _makesureSection();
    final line = RlogText(text, sec);
    sec.addLines([line]);
    refresh();
  }

  void addLines(List<String> text, {RlogSection? section, bool fresh = true}) {
    final sec = section ?? _makesureSection();
    sec.addLines(text.map((e) {
      final line = RlogText(e, sec);
      _canDisp(line);
      return line;
    }));
    if (fresh) refresh();
  }

  void refresh() {
    dispLinesCount.value = caculateDispLinesCount;
    allLinesCount.value = caculateAllLinesCount;
  }

  void updateSshCfg(String ip, String user, String pswd, int port) {
    SshService srv = Get.find();
    srv.updateSshCfg(ip, user, pswd, port);
  }

  Future<AbortCallback?> shellAsync(String ip, String cmd,
      void onLines(List<String> lines), void onCompelet(CmdResult res)) async {
    SshService srv = Get.find();
    return await srv.shellAsync2(ip, cmd, onLines, onCompelet);
  }

  void abortPendingCmd() {
    _lines.forEach((element) {
      if (element.runtimeType == RlogSection) {
        if (element.dat != null) {
          element.dat();
          element.dat = null;
        }
        element.finish = true;
      }
    });
  }

  void removeAllLogLines() {
    abortPendingCmd();
    _lines.clear();
    dispLinesCount.value = 0;
    allLinesCount.value = 0;
  }

  void cleanFilter() {
    _dispFilters.clear();
    _lines.forEach((sec) {
      sec.resetChildrenDisplayable(true);
    });
    dispLinesCount.value = caculateDispLinesCount;
    dispFilterCount.value = 0;
  }

  void enableFilter(FilterRuler ruler, bool en) {
    if (!_dispFilters.contains(ruler)) return;
    if (ruler.enable == en) return;
    ruler.enable = en;
    dispFilterCount.refresh();
  }

  void applyFilters() {
    final hasAddRulers =
        _dispFilters.any((element) => element.enable && !element.isExclude);
    _lines.forEach((sec) {
      sec.resetChildrenDisplayable(!hasAddRulers);
      sec.children.forEach((line) {
        _canDisp(line);
      });
      if (beforeCnt > 0 || afterCnt > 0) {
        _apendBeforAndAfter(sec);
      }
    });
    refresh();
  }

  void _apendBeforAndAfter(RlogSection sec) {
    int before = -1;
    final allf = <FilterRuler>[];
    for (int i = 0; i < sec.childrenCount; i++) {
      var line = sec.childAt(i);
      if (!line.needDisplay) continue;
      if (beforeCnt > 0) {
        for (before = max(i - beforeCnt, before + 1); before < i; before++) {
          final cline = sec.childAt(before);
          cline.needDisplay = true;
          cline.makeTextSpan(allf, _hilight);
        }
      }
      for (int j = 0; j < afterCnt; j++) {
        if (++i >= sec.childrenCount) break;
        line = sec.childAt(i);
        if (line.needDisplay) {
          j = 0;
          continue;
        }
        before = i;
        line.needDisplay = true;
        line.makeTextSpan(allf, _hilight);
      }
    }
  }

  void addFilter(FilterRuler ruler) {
    if (_dispFilters.contains(ruler)) return;
    ruler.enable = true;
    _dispFilters.add(ruler);
    dispFilterCount.value = _dispFilters.length;
  }

  void deleteDispFilter(FilterRuler ruler) {
    if (!_dispFilters.remove(ruler)) return;
    dispFilterCount.value = _dispFilters.length;
  }

  bool _canDisp(Rlogline line) {
    final filterList = enableDispFilters;
    if (filterList.length == 0) {
      line.clearTextSpan();
      return true;
    }
    if (line.runtimeType == RlogSection) return true;
    filterList.forEach((ruler) {
      ruler.testDisplay(line);
    });
    if (line.needDisplay) {
      line.makeTextSpan(filterList, _hilight);
    }
    return line.needDisplay;
  }

  void finishAdd(RlogSection sec) {
    sec.finish = true;
    sec.dat = null;
    dispLinesCount.refresh();
    refresh();
  }

  Iterable<FilterRuler> get dispFilters => _dispFilters;
  Iterable<FilterRuler> get enableDispFilters =>
      _dispFilters.where((ruler) => ruler.enable);

  Rlogline dispLinesAt(int index) {
    int pos = 0;
    Rlogline? line;
    _lines.firstWhere((sec) {
      int endPos = pos + 1 + sec.dispChildrenCount;
      sec.prepareChildren(index - pos - 1);
      if (endPos <= index) {
        pos = endPos;
        return false;
      }
      if (pos == index) {
        line = sec;
      } else {
        if (index - pos - 1 < 0) {
          print("error index: $index");
        }
        //line = sec.displayChilren.elementAt(index-pos-1);
        line = sec.displayChildAt(index - pos - 1);
      }
      return true;
    });
    return line ?? RlogSection("");
  }

  int searchText(String s, int fromIndex, bool resever) {
    final endIndex = dispLinesCount.value - 1;
    if (fromIndex < 0)
      fromIndex = 0;
    else if (fromIndex > endIndex) fromIndex = endIndex;
    int off = 1;
    if (s != _hilight) {
      off = 0;
      _hilight = s;
      final filterList = enableDispFilters;
      for (int i = 0; i < dispLinesCount.value; ++i) {
        final line = dispLinesAt(i);
        line.makeTextSpan(filterList, _hilight);
      }
      Future.delayed(Duration(milliseconds: 300), () {
        dispLinesCount.refresh();
      });
    }
    if (resever) {
      for (int i = fromIndex - off; i >= 0; --i) {
        final line = dispLinesAt(i);
        if (line.text.contains(s)) {
          return i;
        }
      }
    } else {
      for (int i = fromIndex + off; i < dispLinesCount.value; ++i) {
        final line = dispLinesAt(i);
        if (line.text.contains(s)) {
          return i;
        }
      }
    }
    return -1;
  }
}
